package com.xaicode.template.jaeger;

import cn.hutool.core.util.StrUtil;
import io.jaegertracing.internal.JaegerSpan;
import io.jaegertracing.internal.JaegerTracer;
import io.jaegertracing.internal.metrics.Metrics;
import io.jaegertracing.internal.metrics.NoopMetricsFactory;
import io.jaegertracing.internal.reporters.CompositeReporter;
import io.jaegertracing.internal.reporters.RemoteReporter;
import io.jaegertracing.internal.samplers.ConstSampler;
import io.jaegertracing.thrift.internal.senders.HttpSender;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * @author cjxia@isoftstone.com
 * @date 2022/5/9 0009 10:03
 */
@Slf4j
@Component
public class JaegerUtil implements InitializingBean {

    @Value("${opentracing.jaeger.http-sender.url}")
    private String httpSenderUrl;

    @Value("${spring.application.name}")
    private String applicationName;

    private JaegerTracer jaegerTracer;

    private String jaegerUrl() {
        return httpSenderUrl;
    }

    /**
     * 创建指定文件名的日志
     *
     * @param serviceName 文件名
     * @return .
     */
    private JaegerTracer createTracer(String serviceName) {
        HttpSender httpSender = new HttpSender.Builder(jaegerUrl()).build();
        Metrics metrics = new Metrics(new NoopMetricsFactory());
        RemoteReporter remoteReporter = new RemoteReporter.Builder()
                .withFlushInterval(1000)
                .withMaxQueueSize(65000)
                .withSender(httpSender)
                .withMetrics(metrics)
                .build();

        CompositeReporter compositeReporter = new CompositeReporter(remoteReporter);
        ConstSampler constSampler = new ConstSampler(true);
        return new JaegerTracer.Builder(serviceName)
                .withReporter(compositeReporter)
                .withSampler(constSampler)
                .withExpandExceptionLogs()
                .build();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        jaegerTracer = createTracer(applicationName);
    }

    /**
     * 发送 jaeger log
     *
     * @param spanName jaeger Operation
     * @param msg      jaeger log
     * @param tags     complex tag, like [kvkv], eg: name,zhangsan,age,4
     */
    public void send(String spanName, String msg, Tag... tags) {
        if (null == jaegerTracer) {
            jaegerTracer = createTracer(applicationName);
        }

        JaegerSpan span = jaegerTracer.buildSpan(spanName).start();
        for (Tag tag : tags) {
            span.setTag(tag.key, tag.val);
        }
        span.log(msg);
        span.finish();
    }

    // ----

    private final ReentrantLock rLock = new ReentrantLock(true);

    private ConcurrentHashMap<String, LinkedList<Msg>> map = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, AtomicLong> spanRemain = new ConcurrentHashMap<>();

    //    private final long MAX_SPAN_MSG_REMAIN_TIME = 5 * 60 * 1000L;
//    private final int MAX_SPAN_MSG_REMAIN_COUNT = 100;
    private final long MAX_SPAN_MSG_REMAIN_TIME = 1 * 60 * 1000L;
    private final int MAX_SPAN_MSG_REMAIN_COUNT = 3;

    @Data
    public static class Msg {
        private String span;
        private Tag[] tag;
        private String msg;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Tag implements Comparable<Tag> {
        private String key;
        private String val;

        @Override
        public int compareTo(Tag t) {
            if (this.key.compareToIgnoreCase(t.getKey()) > 0) {
                return 1;
            } else if (this.key.compareToIgnoreCase(t.getKey()) < 0) {
                return -1;
            }
            return 0;
        }
    }

    public Tag buildTag(String key, String val) {
        return new Tag(key, val);
    }

    public Msg buildMsg(String span, String msg, Tag... tags) {
        Msg m = new Msg();
        Tag[] sortedTags = Arrays.stream(tags).sorted().toArray(Tag[]::new);

        String tagValFeature = Arrays.stream(sortedTags).map(Tag::getVal).collect(Collectors.joining("-"));

        m.setSpan(span + (StrUtil.isNotBlank(tagValFeature) ? ("-" + tagValFeature) : ""));
        m.setTag(sortedTags);
        m.setMsg(msg);
        return m;
    }

    public void add(Msg msg) {
        try {
            rLock.lock();
            String key = msg.getSpan();
            if (map.containsKey(key)) {
                LinkedList<Msg> l = map.get(key);
                l.add(msg);
                // if send msg
                if (sendMessage(l.size(), spanRemain.get(key).get())) {
                    // reset map.key -> val
                    map.put(key, new LinkedList<>());
                    // reset time
                    spanRemain.put(key, new AtomicLong(System.currentTimeMillis()));

                    // send msg
                    String combineMsgContent = l.stream().map(Msg::getMsg)
                            .collect(Collectors.joining("; "));
                    send(key, combineMsgContent, l.get(0).getTag());
                }
            } else {
                LinkedList<Msg> ll = new LinkedList<>();
                ll.add(msg);
                map.put(key, ll);
                spanRemain.put(key, new AtomicLong(System.currentTimeMillis()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
    }

    private boolean sendMessage(int size, long lastSendMills) {
        return size >= MAX_SPAN_MSG_REMAIN_COUNT
                || System.currentTimeMillis() - lastSendMills >= MAX_SPAN_MSG_REMAIN_TIME;
    }

}
